package com.geeaks.learn.spider;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import com.google.common.collect.Maps;

public class HttpClientSender {
	private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = null;
	
	private static Map<String, CloseableHttpClient> httpClients = new HashMap<String, CloseableHttpClient>();
	
	private static Object o = new Object();
	
	public static String sendHttpPost(String requestUrl,String sendData) throws IOException {
		Map<String, String> headers = Maps.newHashMap();
		headers.put("Content-type","application/json; charset=utf-8");
		Map<String, Object> response = request("POST", requestUrl, sendData, headers);
		int responseCode = Integer.parseInt(response.get("responseCode").toString());
		byte[] responseBytes = (byte[]) response.get("responseData");
		String responseString;
		try {
			responseString = new String(responseBytes, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			responseString = new String(responseBytes);
		}
		// 请求返回结果无论成功失败，http-status均为200
		if (responseCode == 200) {
			// 返回结果
			return responseString;
		} else {
			throw new IOException(responseCode + ":" + responseString);
		}
	}
	
	public static String sendHttpPost(String requestUrl,String sendData,Map<String, String> headers) throws IOException {
		headers.put("Content-type","application/json; charset=utf-8");
		Map<String, Object> response = request("POST", requestUrl, sendData, headers);
		int responseCode = Integer.parseInt(response.get("responseCode").toString());
		byte[] responseBytes = (byte[]) response.get("responseData");
		String responseString;
		try {
			responseString = new String(responseBytes, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			responseString = new String(responseBytes);
		}
		// 请求返回结果无论成功失败，http-status均为200
		if (responseCode == 200) {
			// 返回结果
			return responseString;
		} else {
			throw new IOException(responseCode + ":" + responseString);
		}
	}
	
	public static String sendHttpPost(String host, String method, String urlParams, String sendData) throws IOException {
		String requestUrl = host + method + urlParams;
		Map<String, String> headers = Maps.newHashMap();
		headers.put("Content-type","application/json; charset=utf-8");
		Map<String, Object> response = request("POST", requestUrl, sendData, headers);
		int responseCode = Integer.parseInt(response.get("responseCode").toString());
		byte[] responseBytes = (byte[]) response.get("responseData");
		String responseString;
		try {
			responseString = new String(responseBytes, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			responseString = new String(responseBytes);
		}
		// 请求返回结果无论成功失败，http-status均为200
		if (responseCode == 200) {
			// 返回结果
			return responseString;
		} else {
			throw new IOException(responseCode + ":" + responseString);
		}
	}
	
	public static byte[] sendHttpGet(String host, String method, String urlParams) throws IOException {
		String requestUrl = host + method + urlParams;
		Map<String, Object> response = request("GET", requestUrl, null, null);
		int responseCode = Integer.parseInt(response.get("responseCode").toString());
		byte[] responseBytes = (byte[]) response.get("responseData");
		// 请求返回结果无论成功失败，http-status均为200
		if (responseCode == 200) {
			// 返回结果
			return responseBytes;
		} else {
			throw new IOException(responseCode + "");
		}
	}
	
	public static byte[] sendHttpGet(String host) throws Exception {
		String requestUrl = host;
		Map<String, Object> response = request("GET", requestUrl, null, null);
		int responseCode = Integer.parseInt(response.get("responseCode").toString());
		byte[] responseBytes = (byte[]) response.get("responseData");
		// 请求返回结果无论成功失败，http-status均为200
		if (responseCode == 200) {
			// 返回结果
			return responseBytes;
		} else {
			throw new Exception(responseCode + "");
		}
	}
	
	public static Map<String, Object> request(String method, String url, Object sendData, Map<String, String> headers) throws IOException {
		String requestPath;
		try {
			requestPath = new URL(url).getPath();
		} catch (MalformedURLException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
		CloseableHttpClient httpClient = getHttpClient(requestPath);
		Map<String, Object> response = null;
		if ("POST".equals(method)) {
			response = sendPost(httpClient, url, headers, sendData);
		} else {
			response = sendGet(httpClient, url, headers);
		}
		return response;
	}
	
	public static Map<String, Object> sendPost(CloseableHttpClient httpClient, String url, Map<String, String> headers, Object sendData)
			throws IOException {
		int responseCode = -1;
		byte[] responseBytes = null;
		HttpPost request = new HttpPost(url);
		if (headers != null && headers.size() > 0) {
			for (String name : headers.keySet()) {
				String value = headers.get(name);
				request.setHeader(name, value);
			}
		}
		if (sendData != null) {
			StringEntity stringEntity = new StringEntity((String) sendData, "UTF-8");
			stringEntity.setContentType("application/json;charset=utf-8");
			request.setEntity(stringEntity);
			HttpEntity httpEntity = null;
			IOException exception = null;
			for (int i = 0; i < 3; i++) {
				try {
					CloseableHttpResponse response = httpClient.execute(request);
					responseCode = response.getStatusLine().getStatusCode();
					httpEntity = response.getEntity();
					String responseBody = EntityUtils.toString(httpEntity, "utf-8");
					if (responseBody != null) {
						responseBytes = responseBody.getBytes();
					} else {
						InputStream respStream = null;
						try {
							respStream = httpEntity.getContent();
							int respBodySize = respStream.available();
							if (respBodySize <= 0)
								throw new IOException("Invalid respBodySize: " + respBodySize);
							responseBytes = new byte[respBodySize];
							if (respStream.read(responseBytes) != respBodySize)
								throw new IOException("Read respBody Error");
						} catch (Exception e) {
						} finally {
							if (respStream != null) {
								respStream.close();
							}
						}
					}
					exception = null;
					break;
				} catch (UnsupportedOperationException e) {
					try {
						EntityUtils.consume(httpEntity);
					} catch (IOException e2) {
					}
					throw new RuntimeException(e.getMessage(), e);
				} catch (IOException e) {
					e.printStackTrace();
					exception = e;
					try {
						EntityUtils.consume(httpEntity);
					} catch (IOException e2) {
					}
					if (i < 2) {
						try {
							Thread.sleep(5);
						} catch (InterruptedException e2) {
						}
					}
				}
			}
			if (exception != null) {
				throw exception;
			}
		}
		Map<String, Object> response = new HashMap<String, Object>();
		response.put("responseCode", responseCode);
		response.put("responseData", responseBytes);
		getLoggerString(responseBytes, 256);
		return response;
	}
	
	private static Map<String, Object> sendGet(CloseableHttpClient httpClient, String url, Map<String, String> headers) throws IOException {
		int responseCode = -1;
		byte[] responseBytes = null;
		HttpGet request = new HttpGet(url);
		if (headers != null && headers.size() > 0) {
			for (String name : headers.keySet()) {
				String value = headers.get(name);
				request.setHeader(name, value);
			}
		}
		HttpEntity httpEntity = null;
		IOException exception = null;
		for (int i = 0; i < 3; i++) {
			try {
				CloseableHttpResponse response = httpClient.execute(request);
				responseCode = response.getStatusLine().getStatusCode();
				httpEntity = response.getEntity();
				byte[] responseBody = EntityUtils.toByteArray(httpEntity);
				if (responseBody != null) {
					responseBytes = responseBody;
				} else {
					InputStream respStream = null;
					try {
						respStream = httpEntity.getContent();
						int respBodySize = respStream.available();
						if (respBodySize <= 0)
							throw new IOException("Invalid respBodySize: " + respBodySize);
						responseBytes = new byte[respBodySize];
						if (respStream.read(responseBytes) != respBodySize)
							throw new IOException("Read respBody Error");
					} catch (Exception e) {
					} finally {
						if (respStream != null) {
							respStream.close();
						}
					}
				}
				exception = null;
				break;
			} catch (UnsupportedOperationException e) {
				try {
					EntityUtils.consume(httpEntity);
				} catch (IOException e2) {
				}
				throw new RuntimeException(e.getMessage(), e);
			} catch (IOException e) {
				e.printStackTrace();
				exception = e;
				try {
					EntityUtils.consume(httpEntity);
				} catch (IOException e2) {
				}
				if (i < 2) {
					try {
						Thread.sleep(5);
					} catch (InterruptedException e2) {
					}
				}
			}
		}
		if (exception != null) {
			throw exception;
		}
		Map<String, Object> response = new HashMap<String, Object>();
		response.put("responseCode", responseCode);
		response.put("responseData", responseBytes);
		return response;
	}
	
	private static String getLoggerString(final byte[] data, int maxLength) {
		String loggerString;
		if (data.length > maxLength) {
			byte[] shortData = new byte[maxLength];
			System.arraycopy(data, 0, shortData, 0, shortData.length);
			try {
				loggerString = new String(shortData, "UTF-8") + "...";
			} catch (UnsupportedEncodingException e) {
				loggerString = new String(shortData) + "...";
			}
		} else {
			try {
				loggerString = new String(data, "UTF-8");
			} catch (UnsupportedEncodingException e) {
				loggerString = new String(data);
			}
		}
		char[] chars = new char[loggerString.length()];
		loggerString.getChars(0, loggerString.length(), chars, 0);
		for (int i = 0; i < chars.length; i++) {
			char c = chars[i];
			if (c == '\n' || c == '\r') {
				chars[i] = ' ';
			}
		}
		return new String(chars);
	}
	
	private static CloseableHttpClient getHttpClient(String requestPath) {
		if (httpClients.containsKey(requestPath)) {
			return httpClients.get(requestPath);
		}
		if (poolingHttpClientConnectionManager == null) {
			synchronized (o) {
				if (poolingHttpClientConnectionManager == null) {
					poolingHttpClientConnectionManager = HttpClientUtils.createHttpClientConnectionManager();
				}
			}
		}
		synchronized (httpClients) {
			if (httpClients.containsKey(requestPath)) {
				return httpClients.get(requestPath);
			}
			CloseableHttpClient httpClient = HttpClientUtils.createHttpClient(poolingHttpClientConnectionManager);
			httpClients.put(requestPath, httpClient);
			return httpClient;
		}
	}
	
	private static class HttpClientUtils {
		
		// 默认连接超时
		private static int defaultConnectTimeout = 6000;
		
		// 默认读取超时
		private static int defaultReadTimeout = 30000;
		
		public static CloseableHttpClient createHttpClient(PoolingHttpClientConnectionManager connManager) {
			// HttpHost httpHost = new HttpHost("10.211.55.4", 8888);
			CloseableHttpClient httpClient = HttpClients.custom()
					// .setProxy(httpHost)
					.setConnectionManager(connManager).disableContentCompression().setSSLContext(getSslcontext())
					.setDefaultRequestConfig(getRequestConfig()).build();
			return httpClient;
		}
		
		@SuppressWarnings("deprecation")
		public static PoolingHttpClientConnectionManager createHttpClientConnectionManager() {
			SSLConnectionSocketFactory sslConnectionSocketFactory = null;
			try {
				sslConnectionSocketFactory = new SSLConnectionSocketFactory(getSslcontext(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
			} catch (Exception e) {
				throw new RuntimeException(e.getMessage(), e);
			}
			Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
					.register("https", sslConnectionSocketFactory).register("http", new PlainConnectionSocketFactory()).build();
			PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
			cm.setMaxTotal(500);
			cm.setDefaultMaxPerRoute(500);
			return cm;
		}
		
		private static RequestConfig getRequestConfig() {
			RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectionRequestTimeout(defaultConnectTimeout)
					.setSocketTimeout(defaultReadTimeout).build();
			return defaultRequestConfig;
		}
		
		private static SSLContext getSslcontext() {
			SSLContext sslContext = null;
			try {
				sslContext = SSLContext.getInstance("TLS");
				TrustManager tm = new X509TrustManager() {
					
					public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					}
					
					public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					}
					
					public X509Certificate[] getAcceptedIssuers() {
						return null;
					}
				};
				sslContext.init(null, new TrustManager[] { tm }, null);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return sslContext;
		}
	}
	
}
